const tree = {
  name: '1',
  type: 'root',
  child: [
    {
      name: 'child1',
      type: 'parent',
      child: [
        {
          type: 'son',
          name: 'child1_chile1',
        },
      ],
    },
    {
      name: 'child2',
      type: 'parent',
      child: [
        {
          name: 'child2_chile1',
          type: 'son',
        },
      ],
    },
  ],
}
const visitor1 = {
  root: {
    in: (node) => {
      console.log('rootName in', node.name)
    },
    out: (node) => {
      console.log('rootName out', node.name)
    },
  },
  parent: {
    in: (node) => {
      console.log('parentName in', (node.name += 'parent'))
    },
    out: (node) => {
      console.log('parentName out', (node.name += 'parent'))
    },
  },
}
const visitor = {
  root: (node) => {
    console.log('rootName in', node.name)
  },
  parent: {
    in: (node) => {
      console.log('parentName in', (node.name += 'parent'))
    },
  },
}
const foo = (tree, visitor, visitorKey) => {
  const visitorInFn = (item) => {
    const nodeType = item[visitorKey]
    Object.keys(visitor).forEach((key) => {
      if (key === nodeType) {
        const fnObj = visitor[key]
        const fn = typeof fnObj === 'function' ? fnObj : fnObj.in
        fn(item)
      }
    })
  }
  const visitorOutFn = (item) => {
    const nodeType = item[visitorKey]
    Object.keys(visitor).forEach((key) => {
      if (key === nodeType) {
        const fnObj = visitor[key]
        const fn = fnObj.out
        fn && fn(item)
      }
    })
  }
  const traverse = (item) => {
    visitorInFn(item)
    if (item.child) {
      item.child.forEach((element) => {
        traverse(element)
      })
    }
    visitorOutFn(item)
  }
  traverse(tree)
}
foo(tree, visitor, 'type')
// foo(tree, visitor1, 'type')
